﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Web.UI;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Server control used to generate a jQuery UI Slider widget with a single value.
	/// </summary>
	public class Slider : SliderBase {


		#region Constants: Property defaults

		private const int defaultValue = 0;
		private const SliderFixedRange defaultRange = SliderFixedRange.None;
		private const string defaultValueDisplayControl = "";

		#endregion


		#region Members

		private string valueDisplayControlClientId;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.Slider class.
		/// </summary>
		public Slider() {
			ValueField = null;
		}

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// A value of "Min" higlights the range from the slider min to the handle. A "Max" value displays the range from the handle to the slider max.
		/// This property maps to the "range" option when the jQuery UI widget is initialized.  To set a value of "true" as supported by the widget, use the SliderRange server control instead.
		/// </summary>
		/// <seealso cref="SliderRange"/>
		[DefaultValue(defaultRange), Category("Appearance")]
		public virtual SliderFixedRange Range {
			get { return ViewState.GetValue("Range", defaultRange); }
			set {
				if(!Enum.IsDefined(typeof(SliderFixedRange), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["Range"] = value;
			}
		}

		/// <summary>
		/// Determines the value of the slider.
		/// This property maps directly to the "value" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultValue), Category("Data")]
		public virtual int Value {
			get {
				EnsureChildControls();
				string value = ValueField.Value;
				return string.IsNullOrEmpty(value) ? defaultValue : ParseValue(value);
			}
			set {
				EnsureChildControls();
				ValueField.Value = value.ToString(CultureInfo.InvariantCulture); 
			}
		}

		#endregion


		#region Properties: Extended widget functionality

		/// <summary>
		/// Gets or sets reference to a control that can display the value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string ValueDisplayControl {
			get { return ViewState["ValueDisplayControl"] as string ?? defaultValueDisplayControl; }
			set { ViewState["ValueDisplayControl"] = value; }
		}

		#endregion


		#region Properties: Sub control

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Value property.
		/// </summary>
		protected virtual PostBackField ValueField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Value property.
		/// </summary>
		public virtual string ValueClientId {
			get { return ValueField.ClientID; }
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparatin for posting back or rendering.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			ValueField = new PostBackField();
			ValueField.ID = ID + ClientIDSeparator + "Value";
			ValueField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(ValueField_ValueChanged);
			Controls.Add(ValueField);
		}

		/// <summary>
		/// Raises the System.Web.UI.Control.PreRender event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			valueDisplayControlClientId = this.GetClientId(ValueDisplayControl);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {
			base.RenderLoadScript(script);

			script.NewLine();

			if(!string.IsNullOrEmpty(valueDisplayControlClientId))
				RenderDisplayControlInitScript(script);
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			SliderFixedRange range = Range;
			if(range != defaultRange) properties.AddEnumValue("range", range);

			int value = Value;
			if(value != defaultValue) properties.AddValue("value", value);

			ScriptBuilder slideScript = new ScriptBuilder();
			RenderSlideScript(slideScript);
			if(slideScript.Length != 0) properties.AddScript("slide", slideScript);

			return properties;
		}

		/// <summary>
		/// Renders the client "slide" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderSlideScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(ValueClientId);
			script.Append(".val(ui.value); ");

			if(!string.IsNullOrEmpty(valueDisplayControlClientId)) {
				script.RenderSelector(valueDisplayControlClientId, ":input");
				script.Append(".val(ui.value); ");
				script.RenderSelector(valueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.value); ");
			}

			script.Append("}");

		}

		/// <summary>
		/// Renders the script for initially sychronizing the value of the ValueDisplayControl
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderDisplayControlInitScript(ScriptBuilder script) {

			string clientId = ClientID;
			string displayClientId = valueDisplayControlClientId;

			RenderDisplayFieldInitializeScript(script, ".val(", displayClientId, ":input", clientId);
			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".text(", displayClientId, ":not(:input)", clientId);
		}

		#endregion


		#region Methods: Routines

		private int ConstrainValue(int newValue) {
			return Math.Max(Min, Math.Min(Max, newValue));
		}

		private int ParseValue(string text) {
			int value;
			if(int.TryParse(text,NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) return value;
			return Min;
		}

		private static void RenderDisplayFieldInitializeScript(ScriptBuilder script, string clientFunction, string displayClientId, string displaySelectorSuffix, string clientId) {
			script.RenderSelector(displayClientId, displaySelectorSuffix);
			script.Append(clientFunction);
			script.RenderSelector(clientId);
			script.Append(".slider(");
			script.AppendValue("value");
			script.Append("));");
		}

		#endregion


		#region Event handlers

		private void ValueField_ValueChanged(object sender, PostBackFieldEventArgs e) {

			int oldValue = ParseValue(e.OldValue);
			int newValue = ParseValue(e.NewValue);

			newValue = ConstrainValue(newValue);

			if(newValue == oldValue) return;

			OnValueChanged(EventArgs.Empty);
		}

		#endregion


	}

}